using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Threading;

namespace Sagile.Nepma
{
    /// <summary>
    /// <see cref="TaskManager"/> controls execution of a list of tasks organized as a queue.
    /// Tasks can be added, removed, started and stopped.
    /// </summary>
    public class TaskManager : ITaskManager, IInterruptableProcess, IProgressReporter, INotifyPropertyChanged
    {
        #region Internals

        /// <summary>
        /// Task queue.
        /// </summary>
        private readonly Queue<ISequenceTask> _availableTasks = new Queue<ISequenceTask>();

        /// <summary>
        /// Tasks that have to be executed in the current run.
        /// </summary>
        private readonly Queue<ISequenceTask> _runnableTasks = new Queue<ISequenceTask>();

        /// <summary>
        /// List of finished tasks.
        /// </summary>
        private readonly List<ISequenceTask> _finishedTasks = new List<ISequenceTask>();

        /// <summary>
        /// Lists of tasks interrupted by user action or by an internal error.
        /// </summary>
        private readonly List<ISequenceTask> _abortedTasks = new List<ISequenceTask>();

        /// <summary>
        /// Currently running tasks
        /// </summary>
        private readonly List<ISequenceTask> _runningTasks = new List<ISequenceTask>();

        /// <summary>
        /// Determines if the supervisor must still run.
        /// </summary>
        private volatile bool _runSupervisor = true;

        /// <summary>
        /// Used to pause execution after a milestone
        /// </summary>
        private volatile bool _mustPauseAfterCurrentGroup = false;
        private volatile PausedEventArgs _pausedEventArgs;

        /// <summary>
        /// Thread responsible for supervising execution of tasks, like running groups of tasks, monitoring errors, etc.
        /// </summary>
        private Thread _supervisor = null;

        /// <summary>
        /// Defines if the manager is paused when an error occurs in a running task.
        /// </summary>
        private bool _pauseOnError;

        #endregion Internals

        #region ITaskManager Members

        #region Properties

        /// <summary>
        /// Gets or sets a value indicating whether process must pause when an error occurs.
        /// </summary>
        /// <value><c>true</c> if [pause on error]; otherwise, <c>false</c>.</value>
        public bool PauseOnError
        {
            get { return _pauseOnError; }
            set
            {
                if (value != _pauseOnError)
                {
                    _pauseOnError = value;
                    OnPropertyChanged("PauseOnError");
                }
            }
        }

        /// <summary>
        /// Gets the running tasks count.
        /// </summary>
        /// <value>The running tasks count.</value>
        public int RunningTasksCount
        {
            get { return _runningTasks.Count; }
        }

        /// <summary>
        /// Gets the finished tasks count.
        /// </summary>
        /// <value>The finished tasks count.</value>
        public int FinishedTasksCount
        {
            get { return _finishedTasks.Count; }
        }

        /// <summary>
        /// Gets the aborted tasks count.
        /// </summary>
        /// <value>The aborted tasks count.</value>
        public int AbortedTasksCount
        {
            get { return _abortedTasks.Count; }
        }

        /// <summary>
        /// Gets the remaining tasks count.
        /// </summary>
        /// <value>The remaining tasks count.</value>
        public int RemainingTasksCount
        {
            get { return _runnableTasks.Count; }
        }

        /// <summary>
        /// Gets the tasks count.
        /// </summary>
        /// <value>The tasks count.</value>
        public int TasksCount
        {
            get { return _availableTasks.Count; }
        }

        #endregion Properties

        #region Events

        /// <summary>
        /// Occurs when all tasks have been executed (defined in <see cref="ITaskManager"/>).
        /// </summary>
        public event EventHandler<EventArgs> Completed;

        /// <summary>
        /// Occurs when processing is started (defined in <see cref="ITaskManager"/>).
        /// </summary>
        public event EventHandler<EventArgs> Started;

        /// <summary>
        /// Occurs when a task has started.
        /// </summary>
        public event EventHandler<ManageableTaskEventArgs> TaskStarted;

        /// <summary>
        /// Occurs when a start has finished.
        /// </summary>
        public event EventHandler<ManageableTaskEventArgs> TaskCompleted;

        /// <summary>
        /// Occurs when a task has aborted.
        /// </summary>
        public event EventHandler<ManageableTaskEventArgs> TaskAborted;

        #endregion Events

        #region EventHandlers

        /// <summary>
        /// Called when [completed].
        /// </summary>
        protected virtual void OnCompleted()
        {
            if (null != Completed)
                Completed(this, EventArgs.Empty);
        }

        /// <summary>
        /// Called when [started].
        /// </summary>
        protected virtual void OnStarted()
        {
            if (null != Started)
                Started(this, EventArgs.Empty);
        }

        /// <summary>
        /// Called when a task has started.
        /// </summary>
        protected virtual void OnTaskStarted(ManageableTaskEventArgs e)
        {
            if (null != TaskStarted)
                TaskStarted(this, e);
        }

        /// <summary>
        /// Called when a task has finished.
        /// </summary>
        protected virtual void OnTaskCompleted(ManageableTaskEventArgs e)
        {
            if (null != TaskCompleted)
                TaskCompleted(this, e);
        }

        /// <summary>
        /// Called when a task has aborted.
        /// </summary>
        protected virtual void OnTaskAborted(ManageableTaskEventArgs e)
        {
            if (null != TaskAborted)
                TaskAborted(this, e);
        }

        #endregion EventHandlers

        #region Methods

        /// <summary>
        /// Adds the task.
        /// </summary>
        /// <param name="task">The task.</param>
        public void AddTask(ISequenceTask task)
        {
            if (null == task)
                throw new ArgumentNullException("task");

            _availableTasks.Enqueue(task);
            OnPropertyChanged("TasksCount");
        }

        /// <summary>
        /// Adds the group of tasks.
        /// </summary>
        /// <param name="group">The group.</param>
        public void AddGroup(ISequenceTask[] group)
        {
            if (null == group)
                throw new ArgumentNullException("group");

            foreach (ISequenceTask aTask in group)
            {
                _availableTasks.Enqueue(aTask);
            }
            OnPropertyChanged("TasksCount");
        }

        /// <summary>
        /// Removes the task.
        /// </summary>
        /// <param name="task">The task.</param>
        public void RemoveTask(ISequenceTask task)
        {
            throw new Exception("The current implementation does not support removal of task.");
        }

        /// <summary>
        /// Removes the group.
        /// </summary>
        /// <param name="group">The group.</param>
        public void RemoveGroup(ISequenceTask[] group)
        {
            throw new Exception("The current implementation does not support removal of task.");
        }

        /// <summary>
        /// Clears this instance.
        /// </summary>
        public void ClearTasks()
        {
            lock (_availableTasks)
            {
                _availableTasks.Clear();
                OnPropertyChanged("TasksCount");
            }

            lock (_runnableTasks)
            {
                _runnableTasks.Clear();
                OnPropertyChanged("RemainingTasksCount");
            }

            lock (_abortedTasks)
            {
                _abortedTasks.Clear();
                OnPropertyChanged("AbortedTasksCount");
            }

            lock (_finishedTasks)
            {
                _finishedTasks.Clear();
                OnPropertyChanged("FinishedTasksCount");
            }

            lock (_runningTasks)
            {
                _runningTasks.Clear();
                OnPropertyChanged("RunningTasksCount");
            }
        }

        /// <summary>
        /// Starts the sequence.
        /// </summary>
        public void StartSequence()
        {
            OnStarted();

            lock (_runnableTasks)
            {
                _runnableTasks.Clear();

                foreach (ISequenceTask aTask in _availableTasks)
                {
                    _runnableTasks.Enqueue(aTask);
                }

                OnPropertyChanged("RemainingTasksCount");
            }

            RunSupervisor();
        }

        /// <summary>
        /// Starts the next group.
        /// </summary>
        public void StartNextGroup()
        {
            OnStarted();

            // No more tasks
            if (_availableTasks.Count == 0)
                return;

            int nextGroup = _availableTasks.Peek().ParallelizationGroup;
            bool continuer = true;
            bool first = true;

            do
            {
                // If tasks are available
                if (_availableTasks.Count > 0)
                {
                    // If next task is of same group and parallelizable or if it is the first task
                    if (first ||
                        (_availableTasks.Peek().ParallelizationGroup == nextGroup &&
                         _availableTasks.Peek().CanBeParallelized))
                    {
                        ISequenceTask aTask = _availableTasks.Dequeue();
                        _runnableTasks.Enqueue(aTask);
                    }
                    else
                    {
                        continuer = false;
                    }
                }
                else
                {
                    continuer = false;
                }
                first = false;
            } while (continuer);

            OnPropertyChanged("RemainingTasksCount");

            RunSupervisor();
        }

        /// <summary>
        /// Starts the next task.
        /// </summary>
        public void StartNextTask()
        {
            OnStarted();

            lock (_runnableTasks)
            {
                _runnableTasks.Enqueue(_availableTasks.Dequeue());
            }

            OnPropertyChanged("RemainingTasksCount");

            RunSupervisor();
        }

        /// <summary>
        /// Stops the sequence. This does not stop the currently executing task(s). It only clears pending task(s).
        /// </summary>
        public void StopSequence()
        {
            lock (_runnableTasks)
            {
                _runnableTasks.Clear();
            }
            OnPropertyChanged("RemainingTasksCount");
        }

        /// <summary>
        /// Stops the current group.
        /// </summary>
        public void StopCurrentGroup()
        {
            lock (_runningTasks)
            {
                if (_runningTasks.Count == 0)
                    return;

                int currentGroup = _runningTasks[0].ParallelizationGroup;
                foreach (ISequenceTask aTask in _runningTasks)
                {
                    // Should always be true
                    if (currentGroup == aTask.ParallelizationGroup)
                    {
                        aTask.Stop();
                        _abortedTasks.Add(aTask);
                    }
                }
            }

            OnPropertyChanged("RemainingTasksCount");
            OnPropertyChanged("AbortedTasksCount");
        }

        /// <summary>
        /// Stops the current task.
        /// </summary>
        public void StopCurrentTask()
        {
            StopCurrentGroup();
        }

        #endregion Methods

        #endregion

        #region Task processing

        /// <summary>
        /// Runs the supervisor.
        /// </summary>
        private void RunSupervisor()
        {
            if (null == _supervisor)
            {
                _supervisor = new Thread(ProcessQueue);
                _runSupervisor = true;
                _supervisor.Start();
            }
            else if (!_runSupervisor)
            {
                if (_supervisor.IsAlive)
                    _supervisor.Abort();
                _supervisor = new Thread(ProcessQueue);
                _runSupervisor = true;
                _supervisor.Start();
            }
            else if (_runSupervisor && !_supervisor.IsAlive)
            {
                _supervisor = new Thread(ProcessQueue);
                _runSupervisor = true;
                _supervisor.Start();
            }
        }

        /// <summary>
        /// Processes the queue.
        /// </summary>
        private void ProcessQueue()
        {
            while (_runSupervisor)
            {
                // If tasks still running, wait for them to finish
                lock (_runningTasks)
                {
                    if (_runningTasks.Count > 0)
                    {
                        Thread.Sleep(500);
                        continue;
                    }
                }

                lock (_runnableTasks)
                {
                    // If no more tasks to run, going to sleep
                    if (_runnableTasks.Count == 0)
                    {
                        Thread.Sleep(0);
                        _runSupervisor = false;

                        // Current run is completed
                        OnCompleted();

                        continue; // will end while.
                    }

                    int nextGroup = _runnableTasks.Peek().ParallelizationGroup;
                    // Starts the next task(s)
                    bool continuer = true;
                    bool first = true;

                    do
                    {
                        // If tasks are available
                        if (_runnableTasks.Count > 0)
                        {
                            // If next task is of same group and parallelizable or if it is the first task
                            if (first ||
                                (_runnableTasks.Peek().ParallelizationGroup == nextGroup &&
                                 _runnableTasks.Peek().CanBeParallelized))
                            {
                                // Dequeue the task and run it
                                ISequenceTask aTask = _runnableTasks.Dequeue();

                                #region Remove listeners

                                aTask.Finished -= aTask_Finished;
                                aTask.Aborted -= aTask_Aborted;
                                aTask.TaskProgressChanged -= aTask_ProgressChanged;
                                aTask.TaskExceptionThrown -= aTask_TaskExceptionThrown;

                                #endregion Remove listeners

                                #region Add listeners

                                aTask.Finished += aTask_Finished;
                                aTask.Aborted += aTask_Aborted;
                                aTask.TaskProgressChanged += aTask_ProgressChanged;
                                aTask.TaskExceptionThrown += aTask_TaskExceptionThrown;

                                #endregion Add listeners

                                aTask.Run();
                                OnTaskStarted(new ManageableTaskEventArgs(aTask.UniqueId, aTask.Name, "Started"));

                                _runningTasks.Add(aTask);

                                OnPropertyChanged("RemainingTasksCount");
                                OnPropertyChanged("RunningTasksCount");

                                if (aTask.IsMilestone)
                                {
                                    _mustPauseAfterCurrentGroup = true;
                                    _pausedEventArgs = new PausedEventArgs(PausedReasonType.Milestone, "Milestone");
                                }

                                first = false;
                            }
                            else
                            {
                                continuer = false;
                            }
                        }
                        else
                        {
                            continuer = false;
                        }
                    }
                    while (continuer);
                }
            }
        }

        /// <summary>
        /// Finds the running task by its ID.
        /// </summary>
        /// <param name="id">The id.</param>
        /// <returns></returns>
        private ISequenceTask FindRunningTaskById(string id)
        {
            lock (_runningTasks)
            {
                foreach (ISequenceTask aTask in _runningTasks)
                {
                    if (aTask.UniqueId.Equals(id))
                        return aTask;
                }
            }

            return null;
        }

        /// <summary>
        /// Handles the TaskExceptionThrown event of the aTask control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="Sagile.Nepma.TaskExceptionThrownEventArgs"/> instance containing the event data.</param>
        void aTask_TaskExceptionThrown(object sender, TaskExceptionThrownEventArgs e)
        {
            ISequenceTask errorTask = FindRunningTaskById(e.TaskId);

            if (_pauseOnError)
            {
                _mustPauseAfterCurrentGroup = true;
                _pausedEventArgs = new PausedEventArgs(PausedReasonType.Error, e.InnerException.ToString());
            }

            // Raises event
            OnTaskExceptionThrown(e);

            lock (_abortedTasks)
            {
                _abortedTasks.Add(errorTask);
            }

            lock (_runningTasks)
            {
                _runningTasks.Remove(errorTask);

                // If a task in the current group is a milestone or an error occured,
                // pause occurs after last task in current group is finished or aborted
                if (_mustPauseAfterCurrentGroup && _runningTasks.Count == 0)
                {
                    _runSupervisor = false;
                    _mustPauseAfterCurrentGroup = false;
                    OnPaused(_pausedEventArgs);
                }
            }

            OnPropertyChanged("AbortedTasksCount");
            OnPropertyChanged("RunningTasksCount");
        }

        /// <summary>
        /// Handles the ProgressChanged event of the aTask control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="Sagile.Nepma.TaskProgressChangedEventArgs"/> instance containing the event data.</param>
        void aTask_ProgressChanged(object sender, TaskProgressChangedEventArgs e)
        {
            OnTaskProgressChanged(e);
        }

        /// <summary>
        /// Handles the Aborted event of the aTask control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="ManageableTaskEventArgs"/> instance containing the event data.</param>
        void aTask_Aborted(object sender, ManageableTaskEventArgs e)
        {
            ISequenceTask abortedTask = FindRunningTaskById(e.ID);

            if (null != abortedTask)
            {
                OnTaskAborted(new ManageableTaskEventArgs(abortedTask.UniqueId, abortedTask.Name, "Aborted"));

                lock (_abortedTasks)
                {
                    _abortedTasks.Add(abortedTask);
                }

                lock (_runningTasks)
                {
                    _runningTasks.Remove(abortedTask);

                    // If a task in the current group is a milestone,
                    // pause occurs after last task in current group is finished or aborted
                    if (_mustPauseAfterCurrentGroup && _runningTasks.Count == 0)
                    {
                        _runSupervisor = false;
                        _mustPauseAfterCurrentGroup = false;
                        OnPaused(_pausedEventArgs);
                        _pausedEventArgs = null;
                    }
                }

                OnPropertyChanged("AbortedTasksCount");
                OnPropertyChanged("RunningTasksCount");
            }
        }

        /// <summary>
        /// Handles the Finished event of the aTask control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="ManageableTaskEventArgs"/> instance containing the event data.</param>
        void aTask_Finished(object sender, ManageableTaskEventArgs e)
        {
            ISequenceTask finishedTask = FindRunningTaskById(e.ID);

            if (null != finishedTask)
            {
                OnTaskCompleted(new ManageableTaskEventArgs(finishedTask.UniqueId, finishedTask.Name, "Finished"));

                lock (_finishedTasks)
                {
                    _finishedTasks.Add(finishedTask);
                }

                lock (_runningTasks)
                {
                    _runningTasks.Remove(finishedTask);

                    // If a task in the current group is a milestone,
                    // pause occurs after last task in current group is finished or aborted
                    if (_mustPauseAfterCurrentGroup && _runningTasks.Count == 0)
                    {
                        _runSupervisor = false;
                        _mustPauseAfterCurrentGroup = false;
                        OnPaused(_pausedEventArgs);
                        _pausedEventArgs = null;
                    }
                }

                OnPropertyChanged("FinishedTasksCount");
                OnPropertyChanged("RunningTasksCount");
            }
        }

        #endregion Task processing

        #region IInterruptableProcess Members

        /// <summary>
        /// Occurs when a milestone group is finished or aborted.
        /// </summary>
        public event EventHandler<PausedEventArgs> Paused;

        /// <summary>
        /// Resumes the process.
        /// </summary>
        public void Resume()
        {
            _runSupervisor = true;
            RunSupervisor();
        }

        /// <summary>
        /// Called when processor is paused.
        /// </summary>
        protected void OnPaused(PausedEventArgs e)
        {
            if (null != Paused)
                Paused(this, e);
        }

        #endregion

        #region IProgressReporter Members

        /// <summary>
        /// Occurs when the progress for a task has changed
        /// </summary>
        public event EventHandler<TaskProgressChangedEventArgs> TaskProgressChanged;

        /// <summary>
        /// Raises the <see cref="TaskProgressChanged"/> event.
        /// </summary>
        /// <param name="e">The <see cref="TaskProgressChangedEventArgs"/> instance containing the event data.</param>
        protected virtual void OnTaskProgressChanged(TaskProgressChangedEventArgs e)
        {
            if (null != TaskProgressChanged)
                TaskProgressChanged(this, e);
        }

        #endregion

        #region IExceptionReporter Members

        /// <summary>
        /// Occurs when an exception is raised during task processing.
        /// </summary>
        public event EventHandler<TaskExceptionThrownEventArgs> TaskExceptionThrown;

        /// <summary>
        /// Raises the <see cref="TaskExceptionThrown"/> event.
        /// </summary>
        /// <param name="e">The <see cref="TaskExceptionThrownEventArgs"/> instance containing the event data.</param>
        protected virtual void OnTaskExceptionThrown(TaskExceptionThrownEventArgs e)
        {
            if (null != TaskExceptionThrown)
                TaskExceptionThrown(this, e);
        }

        #endregion

        #region INotifyPropertyChanged Members

        /// <summary>
        /// Occurs when a property is changed.
        /// </summary>
        public event PropertyChangedEventHandler PropertyChanged;

        /// <summary>
        /// Called when a property is changed.
        /// </summary>
        /// <param name="info">The info.</param>
        protected virtual void OnPropertyChanged(String info)
        {
            if (PropertyChanged != null)
                PropertyChanged(this, new PropertyChangedEventArgs(info));
        }

        #endregion

        #region Constructor

        #endregion Constructor
    }
}
